use clap::{ArgAction, Parser};

use crate::tiling::{MissingTilePolicy, TileSelectionPolicy, VacancyPolicy};

/// Program to generate a larger image made up of different image tiles of the
/// same size
#[cfg_attr(test, derive(Clone, Debug))]
#[derive(Parser)]
#[command(author, version, about)]
#[deny(missing_docs)]
pub struct Arguments {
    /// Number of rows
    pub rows: u16,

    /// Number of columns
    pub cols: u16,

    /// Optional connector name that tiles must exhibit towards the border of
    /// the image; if this option is omitted, bordering tiles may have any
    /// connection towards the border.
    #[arg(short, long, value_name = "CONNECTOR_NAME")]
    pub border: Option<String>,

    /// Optional path to a tile configuration file; overrides any configuration
    /// file in the tile directory.
    #[arg(short, long, group = "config_arg_group", value_name = "PATH")]
    pub config: Option<String>,

    /// Optional path to a tile directory; if omitted, assume the current
    /// working directory.
    ///
    /// The directory may contain a tile configuration file named “config.yaml”.
    #[arg(short = 'd', long, value_name = "PATH")]
    pub tile_directory: Option<String>,

    #[rustfmt::skip]
    /// Optional policy name that specifies how to behave in case there is no
    /// tile with a required connectors list.
    ///
    /// MISSING_TILE_POLICY may have one of the following values:
    ///
    /// avoid:{n}
    ///     Try to avoid leaving empty positions in the tile grid. This may
    ///     take a considerable amount of time and memory to finish due to
    ///     backtracking. The run may terminate unsuccessfully in case there
    ///     is no solution.
    ///
    /// exit:{n}
    ///     Exit immediately if there is no fitting tile; this may be useful
    ///     for debugging tile configurations.
    #[arg(short, long, default_value_t = MissingTilePolicy::Avoid)]
    pub missing_tile_policy: MissingTilePolicy,

    /// Do not use any user defined tile configuration; Instead, assume all
    /// tiles have the connectors
    /// { north: “c”, east: “c”, south: “c”, west: “c” } and a weight of 1.
    #[arg(short, long, group = "config_arg_group", action = ArgAction::SetTrue)]
    pub no_config: bool,

    /// Path to the output file.
    #[arg(short, long, value_name = "PATH", default_value = "output/tiled")]
    pub output: String,

    #[rustfmt::skip]
    /// Optional policy name that specifies how to handle vacant areas.
    ///
    /// VACANCY_POLICY may have one of the following values:
    ///
    /// avoid:{n}
    ///     Try to avoid leaving vacant areas in the tile grid. This may take a
    ///     considerable amount of time and memory to finish due to backtracking.
    ///     The run may terminate unsuccessfully in case there is no solution.
    ///
    /// avoid-strict:{n}
    ///     Same as avoid, but searches the entire problem space for a valid
    ///     solution, if necessary, which might be much slower.
    ///
    /// ignore:{n}
    ///     Ignore and vacant areas in the tile grid untouched.
    #[arg(short, long, default_value_t = VacancyPolicy::Avoid)]
    pub vacancy_policy: VacancyPolicy,

    #[rustfmt::skip]
    /// Optional policy name that specifies how to select tiles in case multiple
    /// fit into a slot.
    ///
    /// TILE_SELECTION_POLICY may have one of the following values:
    ///
    /// random:{n}
    ///     Select a fitting tile at random using the provided weights. The tile
    ///     weights need to be non-negative and their sum must not be zero.
    ///
    /// priority:{n}
    ///     Select the tile with the greatest weight that fits.
    #[arg(long, default_value_t = TileSelectionPolicy::Random)]
    pub tile_selection_policy: TileSelectionPolicy,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tests::fixtures::{
        RANDOM_BOOL,
        RANDOM_MISSING_TILE_POLICY,
        RANDOM_STRINGS,
        RANDOM_TILE_SELECTION_POLICY,
        RANDOM_USIZES,
        RANDOM_VACANCY_POLICY,
    };

    #[test]
    fn test_initialization() {
        let arguments = Arguments {
            rows: RANDOM_USIZES[0] as u16 + 1,
            cols: RANDOM_USIZES[1] as u16 + 1,
            config: Some(RANDOM_STRINGS[0].clone()),
            border: Some(RANDOM_STRINGS[1].clone()),
            missing_tile_policy: RANDOM_MISSING_TILE_POLICY.clone(),
            no_config: *RANDOM_BOOL,
            output: RANDOM_STRINGS[2].clone(),
            tile_directory: Some(RANDOM_STRINGS[3].clone()),
            vacancy_policy: RANDOM_VACANCY_POLICY.clone(),
            tile_selection_policy: RANDOM_TILE_SELECTION_POLICY.clone(),
        };

        assert_eq!(RANDOM_USIZES[0] as u16 + 1, arguments.rows);
        assert_eq!(RANDOM_USIZES[1] as u16 + 1, arguments.cols);
        assert_eq!(Some(RANDOM_STRINGS[0].clone()), arguments.config);
        assert_eq!(Some(RANDOM_STRINGS[1].clone()), arguments.border);
        assert_eq!(
            arguments.missing_tile_policy,
            RANDOM_MISSING_TILE_POLICY.clone()
        );
        assert_eq!(arguments.no_config, *RANDOM_BOOL);
        assert_eq!(arguments.output, RANDOM_STRINGS[2].clone());
        assert_eq!(arguments.tile_directory, Some(RANDOM_STRINGS[3].clone()));
        assert_eq!(arguments.vacancy_policy, RANDOM_VACANCY_POLICY.clone());
        assert_eq!(
            arguments.tile_selection_policy,
            RANDOM_TILE_SELECTION_POLICY.clone()
        );
    }
}
